#include <iostream>

#include <string.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>

#include "PaReader.h"
#include "data.h"

using namespace std;


static void my_pa_stream_read_callback(pa_stream *s, size_t l, void * userData)
{
    my_pa_data *paData = (my_pa_data *) userData;

    unsigned char *p;

    if (pa_stream_peek(s, (const void **)&p, &l) < 0)
    {
        const char *str_error = pa_strerror(pa_context_errno(paData->Context));
        cout << "my_pa_stream_read_callback.pa_stream_peek failed: " << str_error << endl;
        return;
    }

    pa_usec_t latTBefore = 0;
    pa_usec_t latT = 0;
    int latNeg = 0;
    int res;
    if ( (res = pa_stream_get_latency(s, &latT, &latNeg)) != PA_OK)
    {
        if (latNeg > 0)
        {
            cout << "my_pa_stream_read_callback: latency " << latNeg << " " << latT << endl;
        }
    }
    else
    {
        cout << "my_pa_stream_read_callback: pa_stream_get_latency failed " << res << endl;
    }

//    cout << "my_pa_stream_read_callback: " << l << endl;

    int il = (int) l;
    int srcLen = (int)l;
    while (srcLen > 0)
    {
        int BytesToCopy = min(srcLen, (int)(paData->BytesForDft - paData->curDataBufPos));
        memcpy(paData->curDataBuf + paData->curDataBufPos, p + (il - srcLen), BytesToCopy);
        srcLen -= BytesToCopy;
        paData->curDataBufPos += BytesToCopy;

        if (paData->curDataBufPos >= paData->BytesForDft -1)
        {
            /*
            if (latTBefore - latT < paData->DftMsecs * 1000)
            {
                usleep(paData->DftMsecs * 1000 - (latTBefore - latT));
            }
            */
            //cout << "Send data: " << paData->curDataBufPos << " " << il << endl;
            void *pOverflowData = paData->queRawAudioData->addAndGetIfFull(paData->curDataBuf);
            if (pOverflowData != NULL)
            {
                cout << "paData->queRawAudioData->addAndGetIfFull overflow " << paData->queRawAudioData->getSize() << endl;
                free(pOverflowData);
            }
            /*
            if (!paData->queRawAudioData->addIfPossible(paData->curDataBuf))
            {
                cout << "paData->queRawAudioData->addIfPossible failed " << paData->queRawAudioData->getSize() << endl;
                free(paData->curDataBuf);
            }
            */

            paData->curDataBuf = (unsigned char *) malloc(paData->BytesForDft);
            paData->curDataBufPos = 0;
            latTBefore = latT;
        }
    }

    //mainWindow->pushData((const float*) p, l/sizeof(float));

    pa_stream_drop(s);
}

static void my_pa_stream_state_callback(pa_stream *s, void * userData)
{
    my_pa_data *paData = (my_pa_data *) userData;

    switch (pa_stream_get_state(s))
    {
        case PA_STREAM_UNCONNECTED:
            cout << "my_pa_stream_state_callback: PA_STREAM_UNCONNECTED " << paData->stream << endl;
            break;
        case PA_STREAM_CREATING:
            cout << "my_pa_stream_state_callback: PA_STREAM_CREATING " << paData->stream << endl;
            break;

        case PA_STREAM_READY:
            cout << "my_pa_stream_state_callback: PA_STREAM_READY " << paData->stream << endl;
            break;
        /*
            g_assert(!mainWindow);
            mainWindow = new MainWindow(*pa_stream_get_channel_map(s), device_name, device_description);

            g_timeout_add(100, latency_func, NULL);
            pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_info_callback, NULL));
        */
            break;

        case PA_STREAM_FAILED:
            cout << "my_pa_stream_state_callback: PA_STREAM_FAILED " << paData->stream << endl;
            break;

        case PA_STREAM_TERMINATED:
            cout << "my_pa_stream_state_callback: PA_STREAM_TERMINATED " << paData->stream << endl;
            pa_mainloop_quit(paData->MainLoop, -40);
            break;
    }
}


static void create_stream(const char *name, const char *description, const pa_sample_spec &nss, const pa_channel_map &cmap, my_pa_data *paData)
{
    cout << "create_stream: pa_stream_new " << endl;
    paData->stream = pa_stream_new(paData->Context, "PaDft-CaptureStream", &nss, &cmap);
    paData->steamSpecs = nss;
    paData->streamBytesPerSec = pa_bytes_per_second(&(paData->steamSpecs));
    paData->streamSampleBytes = pa_sample_size(&(paData->steamSpecs));
    cout << "create_stream: Bytes/s " << paData->streamBytesPerSec << " @ " << ((int)nss.channels) << " chnls * " << nss.rate  << " Hz * " << paData->streamSampleBytes << " bytes/sample " << endl;

    paData->dft = new DftCalc(paData->DftMsecs, nss.rate, paData->streamSampleBytes, ((int)nss.channels));

    paData->BytesForDft = paData->dft->getDftWindow_NumTimeSamplesBytes();
    if (paData->curDataBuf != NULL)
        free(paData->curDataBuf);

    paData->curDataBufPos = 0;
    paData->curDataBuf = (unsigned char *) malloc(paData->BytesForDft);
    cout << "reate_stream: DftWindow " << paData->DftMsecs << "ms = " << paData->BytesForDft << " bytes" << endl;

    pa_stream_set_state_callback(paData->stream, my_pa_stream_state_callback, paData);
    pa_stream_set_read_callback(paData->stream, my_pa_stream_read_callback, paData);

    cout << "create_stream: pa_stream_connect_record " << paData->stream <<  endl;
    pa_stream_connect_record(paData->stream, name, NULL, (enum pa_stream_flags) 0);
}


static void my_pa_context_get_sink_info_callback(pa_context *c, const pa_sink_info *si, int is_last, void * userData)
{
    my_pa_data *paData = (my_pa_data *) userData;

    if (si == NULL && is_last == 1) // FIXME, this is the second call, but where is this weird behaviour documented, anyway ???
        return;

    if (is_last < 0)
    {
        cout << "my_pa_context_get_sink_info_callback failed: is_last " << is_last << " < 0 " << endl;
        pa_mainloop_quit(paData->MainLoop, -20);
        return;
    }

    if (!si)
    {
        cout << "my_pa_context_get_sink_info_callback failed: server_info is null " << is_last << endl;
        pa_mainloop_quit(paData->MainLoop, -21);
        return;
    }

    cout << "my_pa_context_get_sink_info_callback --> create_stream for " << si->index << si->name << endl;
    create_stream(si->monitor_source_name, si->description, si->sample_spec, si->channel_map, paData);
}

static void my_pa_context_get_server_info_callback(pa_context *c, const pa_server_info *si, void * userData)
{
    my_pa_data *paData = (my_pa_data *) userData;

    if (!si)
    {
        cout << "my_pa_context_get_server_info_callback failed: server_info is null " << endl;
        pa_mainloop_quit(paData->MainLoop, -10);
        return;
    }

    cout << "my_pa_context_get_server_info_callback --> get_sink_info for " << si->default_sink_name << endl;
    pa_operation *op = pa_context_get_sink_info_by_name(c, si->default_sink_name, my_pa_context_get_sink_info_callback, userData);
    pa_operation_unref(op);

}


static void my_pa_context_state_callback(pa_context *c, void * userData)
{
    my_pa_data *paData = (my_pa_data *) userData;
    pa_operation *op;

    switch (pa_context_get_state(c))
    {
        case PA_CONTEXT_UNCONNECTED:
            cout << "my_pa_context_state_callback: PA_CONTEXT_UNCONNECTED" << endl;
            break;
        case PA_CONTEXT_CONNECTING:
            cout << "my_pa_context_state_callback: PA_CONTEXT_CONNECTING" << endl;
            break;
        case PA_CONTEXT_AUTHORIZING:
            cout << "my_pa_context_state_callback: PA_CONTEXT_AUTHORIZING" << endl;
            break;
        case PA_CONTEXT_SETTING_NAME:
            cout << "my_pa_context_state_callback: PA_CONTEXT_SETTING_NAME" << endl;
            break;

        case PA_CONTEXT_READY:
            cout << "my_pa_context_state_callback: PA_CONTEXT_READY --> get_server_info " << endl;
            op = pa_context_get_server_info(c, my_pa_context_get_server_info_callback, userData);
            pa_operation_unref(op);

            //pa_mainloop_quit(m, 13);
        /*
            g_assert(!stream);

            if (device_name && mode == RECORD)
                pa_operation_unref(pa_context_get_source_info_by_name(c, device_name, context_get_source_info_callback, NULL));
            else if (device_name && mode == PLAYBACK)
                pa_operation_unref(pa_context_get_sink_info_by_name(c, device_name, context_get_sink_info_callback, NULL));
            else
                pa_operation_unref(pa_context_get_server_info(c, context_get_server_info_callback, NULL));
        */
            break;

        case PA_CONTEXT_FAILED:
            cout << "my_pa_context_state_callback: PA_CONTEXT_FAILED" << endl;
            pa_mainloop_quit(paData->MainLoop, -2);
            break;

        case PA_CONTEXT_TERMINATED:
            cout << "my_pa_context_state_callback: PA_CONTEXT_TERMINATED" << endl;
            pa_mainloop_quit(paData->MainLoop, -3);
            break;
    }
}


void *thr_read_from_pa(void *UserData)
{
    my_pa_data *paData = (my_pa_data *) UserData;
    int PaRetVal;

    cout << "thr_read_from_pa: Connect to PulseAudio Server" << endl;
    paData->MainLoop = pa_mainloop_new();
    if (!paData->MainLoop)
    {
        cout << "thr_read_from_pa: ERROR: pa_mainloop_new failed " << endl;
        paData->queRawAudioData->setSourceIsFinished();
        return NULL;
    }
    cout << "thr_read_from_pa: PaMainLoop created: " << paData->MainLoop << endl;

    paData->MainLoop_Api = pa_mainloop_get_api(paData->MainLoop);
    if (!paData->MainLoop_Api)
    {
        cout << "thr_read_from_pa: ERROR: pa_mainloop_get_api failed " << endl;
        paData->queRawAudioData->setSourceIsFinished();
        return NULL;
    }
    cout << "thr_read_from_pa: PaApi retrieved: " << paData->MainLoop_Api << endl;

    paData->Context = pa_context_new(paData->MainLoop_Api, "PaDft");
    if (!paData->Context)
    {
        cout << "thr_read_from_pa: pa_context_new failed " << endl;
        paData->queRawAudioData->setSourceIsFinished();
        return NULL;
    }
    cout << "thr_read_from_pa: PaContext created: " << paData->Context << endl;

    pa_context_set_state_callback(paData->Context, my_pa_context_state_callback, paData);
    PaRetVal = pa_context_connect(paData->Context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
    cout << "thr_read_from_pa: context connection initiated: " << PaRetVal << endl;

    cout << "thr_read_from_pa: MainLoop start" << endl;
    pa_mainloop_run(paData->MainLoop, &PaRetVal);
    cout << "thr_read_from_pa: MainLoop ended: " << PaRetVal << endl;

    pa_context_unref(paData->Context);
    pa_mainloop_free(paData->MainLoop);
    cout << "thr_read_from_pa: PaMainLoop deleted " << endl;

    paData->queRawAudioData->setSourceIsFinished();

    cout << "thr_read_from_pa: End ?" << endl;
    return NULL;
}




